Fallbeispiel Gewinnverteilung

[Dies ist ein Fallbeispiel, das in einem separaten Fenster angezeigt wird. So können Sie das Beispiel und ein beliebiges anderes Hilfethema gleichzeitig betrachten. Das Fenster des Fallbeispiels lässt sich verschieben, in seiner Grösse ändern und über das Schliessfeld verlassen]

Im Mittelpunkt dieses grösseren Programms steht die Gewinnverteilung einer Aktiengesellschaft (AG) nach dem Artikel 671 des schweizerischen Obligationenrechts (OR). Weil das Programm - anders als ein reales Buchhaltungspaket - nicht auf dem laufenden Verkehr der Buchungsperiode aufbauen kann, muss es die Ausgangsdaten der Gewinnverteilung vom Benutzer einlesen. Wir ergänzen deshalb die Objektklassen der Hilfethemen Objekte und Benutzerschnittstelle um Klassen, die benutzerdefinierte Eingabeformulare ermöglichen.

Der Zyklus der doppelten Buchhaltung besteht aus drei Phasen:

Die Buchhaltung einer AG arbeitet mit sechs Erfolgsverteilungskonten:

Bei der Präsentation des Beispiels folgen wir den folgenden Entwicklungsphasen:

  1. Spezifikation

  2. Entwurf und

  3. Implementation (vgl. auch das Hilfethema Modularisierung).


Spezifikation

Der folgende Bildschirmausschnitt beschreibt den Zweck von Gewinnverteilung.xls. Die Beschreibung ist Teil der Eigenschaften des Objekts “Arbeitsmappe Gewinnverteilung.xls” (Menüpunkt »Datei/Eigenschaften). Der Entwickler trägt diese Angaben zu Beginn des Projekts ein.

Dieses Formular erlaubt allerdings nur eine knappe Grobspezifikation des Projekts. Detaillierter fällt eine Feinspezifikation aus. Die folgende Spezifikation geht nach dem EVA-Prinzip des Hilfethemas Modularisierung vor und berücksichtigt die gesetzlichen Vorschriften und die kaufmännische Praxis einer konkreten Gewinnverteilungsaufgabe: Der Artikel 671 des schweizerischen Obligationenrechts und die Gewohnheiten der Branche und des Unternehmens bestimmen, wie der Erfolg auf die erwähnten Gewinnverteilungskonten verteilt wird.


Eingabe

Der Einfachheit halber geben wir konkrete Eingabedaten vor:

Beteiligte Konten

Beispielsaldi

Aufwand (Unternehmungsaufwand)

150’000.-

Ertrag (Unternehmungsertrag)

180’000.-

Aktienkapital (Grundkapital)

100’000.-

Reserve (Reservefonds)

5’000.-

Tantième in %

10%

Ausgabe

Alle Buchungssätze der Gewinnverteilung werden im Format “<Sollkonto> an <Habenkonto>, <Betrag>” auf dem Tabellenblatt ausgegeben:

Verarbeitung

Artikel 671 des schweizerischen Obligationenrechts (OR) regelt die Erfolgsverteilung von Aktiengesellschaften. Die Ziffern des folgenden Gesetztestexts verweisen auf den entwurfssprachlichen Algorithmus. Relative Reservezuweisungen sind unterstrichen:

“Aus dem Reingewinn ist jährlich ein Betrag von einem Zwanzigstel einem allgemeinen Reservefonds zuzuweisen (1), bis dieser Fonds die Höhe von einem Fünftel des einbezahlten Grundkapitals erreicht hat (2).

Diesem Reservefonds sind, auch nachdem er die gesetzliche Höhe erreicht hat, zuzuweisen: ...

ein Zehntel derjenigen Beträge (3), die aus dem Reingewinn nach der ordentlichen Speisung des Reservefonds (1) und nach Bezahlung einer Dividende von fünf vom Hundert an Aktionäre (4) und sonstige Gewinnbeteiligte (5) verteilt werden.”

Die folgende Regelung ergänzt OR 671. Sie ist gesetzlich nicht bindend, wird aber oft in der Praxis angewendet:

“Nach der ordentlichen Reservezuweisung (1) erhält der Verwaltungsrat eine Tantième in % des Gewinns (5). Nach der zweiten Speisung der Reserve (3) sind vom Gewinnrest soviele ganze Prozent vom einbezahlten Grundkapital wie möglich als Zusatzdividende auszuschütten (6).”

Wer die Gewinnverteilung nach OR 671 programmieren möchte, steht vor einem Problem, das typisch für die Softwareentwicklung ist: Verbale Spezifikationen sind selten eindeutig. Die Interpretation des obigen Gesetztestext ist zum Beispiel aus den folgenden Gründen schwierig:

Die OR 671 ergänzende Praxisregelung schreibt vor, dass vom Gewinnrest soviele ganze Prozent Zusatzdividende wie möglich auszuschütten seien. Diese Ausschüttung erfordert eine weitere Reservezuweisung von 0.1*Zusatzdividende. Die Zusatzdividende berechnet sich in drei Schritten:

Der genaue Zusatzdividendensatz ergibt sich aus den folgenden Gleichungen:

Zusatzdividende = ZusatzdivSatz * 0.01AK (1)
ZusatzdivSatz = (GV - ResAufZusatzdiv) / 0.01AK (2)
ResAufZusatzdiv = 0.1 * ZusatzdivSatz * 0.01AK (3)

(2) berechnet den genauen Zusatzdividendensatz, der sich nach Abzug der Reservenzuweisung ResAufZusatzdiv noch aus dem Gewinnrest GV ausschütten lässt. (3) formalisiert die Reservevorschrift von OR 671.

Einsetzen von (3) in (2) ergibt den Zusatzdividendensatz:

ZusatzdivSatz = (GV - 0.1 * ZusatzdivSatz * 0.01AK) / 0.01 AK
ZusatzdivSatz = GV / 0.01 AK - 0.1 * ZusatzdivSatz
1.1 ZusatzdivSatz = GV / 0.01AK
ZusatzdivSatz = GV / 0.01AK / 1.1 = GV / 0.011AK.

Im zweiten Schritt bestimmen wir den ganzzahligen Anteil des Zusatzdividendensatzes. Die VBA-Funktion Int(eger) liefert den ganzzahligen Anteil ihres Arguments; Int(99.8) ergibt zum Beispiel 99. Den ganzzahligen Zusatzdividendensatz berechnen wir deshalb als Int(GV / 0.011AK). Die Formel (1) zur Berechnung der Zusatzdividende lautet dann:

Zusatzdividende = Int(GV / 0.011AK) * 0.01AK.


Entwurf

Die Arbeitsmappe Gewinnverteilung.xls setzt sich aus den GUI-Objekten Tabellenblatt und Eingabeformular sowie dem Algorithmus der Gewinnverteilung zusammen:

Tabellenblatt Dialogblatt

Formular Eingabeformular (engl. user form)

Modul Verarbeitungsmodul

Bevor wir die Gewinnverteilung als VBA implementieren, formulieren wir eine erste entwurfssprachliche Annäherung an den Kernalgorithmus. Dabei verwenden wir die folgenden Abkürzungen (Kommentare stehen vor Hochkommas und sind grün):

'ER     Erfolgsrechnung
'GV     Gewinnverteilung
'AK     Aktienkapital
'T%     Tantième in %
'kursiv Buchungssätze
Falls Aufwand < Ertrag dann                          'Gewinn
  Gewinn = Ertrag - Aufwand
  ER an GV, Gewinn
  Falls Reserve < AK / 5 dann                             '(2)
    GV an Reserve, Gewinn / 20                            '(1)
    GV an Dividende, Aktienkapital / 20                   '(4)
    GV an Tantième, Gewinn * T% / 100                     '(5)
    GV an Reserve, Gewinn * T% / 100 / 10                 '(3)
    ZusatzDivSatz =  GV / 0.011AK
    Zusatzdividende = AK * Int (ZusatzDivSatz) / 100
    GV an Dividende,  Zusatzdividende                   '(5,6)
    GV an Reserve, Zusatzdividende / 10                   '(3)
  Falls GV > 0 dann
    GV an Reserve, GV
sonst                                                'Verlust
  Verlust = Aufwand - Ertrag
  Falls Verlust < Reserve dann
    Reserve an ER, Verlust
  sonst
    Reserve an ER, Reserve
    GV an ER, Verlust - Reserve
Gewinnverteilung gemäss OR 671 und Praxis (erste Annäherung)

Wer diesen Algorithmus mit der Subroutine Buchungssätze()der Arbeitsmappe Gewinnverteilung.xls vergleicht, stösst auf Unterschiede. Die programmiersprachliche Version prüft nämlich noch für jeden Buchungssatz, ob der laufende Saldo des Gewinnverteilungskontos für die Buchung ausreicht. Um das Verständnis zu erleichtern lässt der obige Entwurf diese Prüfung weg. Er skizziert nur den algorithmischen Kern.

Der grösste Teil des Code von Gewinnverteilung.xls widmet sich der Benutzeroberfläche. Das folgende Bild zeigt auf dem Hintergrund die vier Spalten des Tabellenblatts “Dialogblatt”. Die Ausgabeanweisung Range(Zelle).Value = Wert schreibt jeden vom Programm ermittelten Wert in die passende Zelle dieses Tabellenblatts. Ein Klick auf die Schaltflläche ‘Start’ ruft die Ereignisprozedur Start_Click(), welche das Eingabeformular anzeigt. Auf dem farbigen Hintergrund des Tabellenblatts sehen Sie ...

  • ein Eingabeformular mit fünf weissen Textfeldern
  • ein Meldungsfeld mit der Reaktion auf einen Eingabefehler.

Eingabe- und Ausgabebereich von Gewinnverteilung.xls

Wie ein Tabellenblatt stellt auch ein benutzerdefiniertes Formular Objekte, Eigenschaften und Methoden bereit. Das obige Eingabeformular “Falldaten eingeben” enthält für jede Eingabe ein Bezeichnungsfeld und ein Textfeld (weisses Feld rechts vom Bezeichnungsfeld). Das programmtechnische Lesen eines Textfelds ist einfach: die Nennung des Namens - zum Beispiel Aufwand - genügt. VBA vervollständigt Aufwand automatisch zu Aufwand.Value, wobei Value jene Eigenschaft ist, welche den laufenden Wert des Felds enthält.

Das nächste Bild zeigt den Entwurf des oben gezeigten Eingabeformulars. Der Entwurf verläuft in vier Schritten:

1. Entwicklungsumgebung aufrufen (»Alt/F11)
2. Leeren Formularrahmen zeichnen
3. Steuerlemente in den Formularrahmen setzen (Bezeichnungs- und Textfelder; Schaltflächen)
4. Den Steuerelementen Ereignisprozeduren zuordnen
(abbrechen_Click, verbuchen_Click).

Das entworfene Formular lässt sich mit Show anzeigen und mit Hide vorübergehend ausblenden.

Entwurf des Eingabeformulars "Falldaten eingeben"


Implementation

Nach dem Entwurf des algorithmischen Kerns und der Benutzeroberfläche bereiten wir im nächsten Entwurfscode die Programmierung in VBA vor. Elemente des Objektmodells von MS Excel sind blau, Schaltflächen stehen zwischen Hochkommas und Ereignisprozeduren sind rot. Für die Subroutine Buchungssätze() gilt dasselbe wie für den Kernalgorithmus: Sie unterscheidet sich von der programmiersprachlichen Variante, weil sie nicht für jeden Buchungssatz kontrolliert, ob der Saldo des Kontos Gewinnverteilung für die jeweilige Buchung noch ausreicht.

Ereignisprozedur Start_Click()
  Ausgabebereich von Dialogblatt löschen
  Eingabeformular mit ‘Abbrechen’ und ‘Verbuchen’ anzeigen
  Falls abgebrochen <> True dann Buchungssätze()
Ereignisprozedur abbrechen_Click()
  Eingabeformular nach Klick auf ‘Abbrechen’ schliessen
  abgebrochen = True
Ereignisprozedur verbuchen_Click()
  ‘--- Leere und nichtnumerische Eingaben verhindern
  Für jedes Feld von Eingabeformular
    Falls leer oder nichtnumerisch dann
      Formular nach Klick auf ‘Verbuchen’ nicht schliessen
      Fehlermeldung zeigen
  Kontrolle an den Benutzer zurückgeben
Subroutine Buchungssätze()
  Eingabeformular lesen (Aufwand, Ertrag, Aktienkapital, Reserve, Tantième in %)
  '-- Buchungssätze gemäss OR 671 auf Dialogblatt schreiben
  Falls Gewinn dann
    BS "ER", "GV", Gewinn 'Reingewinn
    Falls Reserve < AK dann
      BS "GV", "Reserve", Gewinn / 20
    BS "GV", "Dividende", Aktienkapital / 20
    BS "GV", "Reserve", Gewinn / 20 / 10
    BS "GV", "Tantieme", Gewinn * Tantiemensatz / 100
    BS "GV", "Reserve", Gewinn * Tantiemensatz / 100 / 10
    BS "GV", "Dividende", AK * Int(GV / 0.011AK) / 100
    BS "GV", "Reserve", AK * Int(GV / 0.011AK) / 100 / 10
    Falls GV < 0 dann
      "GV", "Reserve", GV
  sonst ‘Verlust
    Falls Verlust < Reserve DANN
      BS "Reserve", "ER", Verlust
    sonst
      BS "Reserve", "ER", Reserve
      BS "GV", "ER", Verlust - Reserve
Subroutine BS(Sollkonto, Habenkonto, Buchungsbetrag)
  Buchungssatz auf Dialogblatt schreiben
  Konto Gewinnverteilung nachführen
Prozeduraler Entwurf (zweite Annäherung. Objekt, Ereignisprozedur, ‘Schaltfläche’)

Wir betrachten den VBA-Code in drei Schritten:

  • Wir unterscheiden zwischen Variablen, die für das ganze Projekt, für ein Modul oder nur eine Prozedur gelten und erklären die Funktion der Ereignisprozedur Start_Click des Tabellenblatts.

  • Wir betrachten die übrigen Ereignisprozeduren des Eingabeformulars.

  • Wir gewinnen einen Überblick über das ganze Programmierprojekt Gewinnverteilung und lernen dabei den Projektexplorer und den Objektkatalog kennen.

Der folgende Code-Abschnitt deklariert zu Beginn die Variable abgebrochen als öffentlich (engl. Public), damit sie im ganzen Projekt, das heisst sowohl im Modul Dialogblatt als auch im Modul Eingabeformular, verwendet werden kann. Ausgabezeile und GV sind hingegen Private-Variablen, weil sie je nur in einem einzigen Modul vorkommen.

' --- Public für alle Module sichtbar
Public abgebrochen As Boolean   ' True, falls “Abbrechen”

' --- Private nur für das Verarbeitungsmodul sichtbar
Private Ausgabezeile As Integer
Private GV As Currency          ' Konto Gewinnverteilung

Sub Start_Click()
  ' --- Dialogblatt und Eingabeformular initialisieren
  GV = 0
  abgebrochen = False
  ' Ausgabebereich des Dialogblatts löschen
  Dialogblatt.Range("A4:F11").ClearContents
  ' Eingabeformular die Kontrolle übergeben
  Eingabeformular.Show          ' laden und anzeigen
  ' --- Gefülltes Eingabeformular verarbeiten
  If abgebrochen = False Then
    Buchungssätze
    Unload Eingabeformular      ' aus Speicher entfernen
  End If
End Sub

Eine Variable, die nur in jener Einheit (Modul oder Prozedur) sichtbar ist, in der sie vereinbart worden ist, heisst lokal. Der Programmierer darf zum Beispiel eine private Variable nur in jenem Modul verwenden, in dem er sie eingeführt hat. Wenn er hingegen eine Variable als Public vereinbart, dann darf er sie auch ausserhalb des deklarierenden Moduls ansprechen. Variablen, die auch ausserhalb der deklarierenden Einheit gelten, heissen global.

Die Schlüsselwörter Public und Private werden selten gebraucht. Häufiger ist das Schlüsselwort Dim. Es dient der Vereinbarung einer Variablen innerhalb einer einzigen Prozedur. Man sagt auch, der Gültigkeitsbereich einer Dim-Variablen beschränke sich auf die Prozedur. Private deklariert hingegen Variablen, die für alle Prozeduren eines Moduls gelten. Public macht sogar eine Vereinbarung für alle Module eines Projekts - zum Beispiel aller Module des Projekts “Gewinnverteilung” - sichtbar.

Die Ereignisprozedur Start_Click() des obigen Programmausschnitts lädt und zeigt das Eingabeformular mit dem Befehl Eingabeformular.Show. Die Codezeile Unload Eingabeformular entfernt das Formular-Steuerelement erst, nachdem der Benutzer die Dateneingabe regulär abgeschlossen hat oder die Schaltfläche ‘Abbrechen’ geklickt hat. Das Programm erkennt am Wert True der Public-Variable abgebrochen, dass der Benutzer das Eingabeformular irregulär verlassen hat.

Der nächste VBA-Ausschnitt enthält die Ereignisprozeduren der beiden Schaltflächen ‘Abbrechen’ und ‘Verbuchen’. Verbuchen_Click() prüft nur die Gültigkeit der Daten des Eingabeformulars. Den Aufruf der Kernprozedur Buchungssätze finden Sie in der Steuerprozedur Start_Click().

'Modul Eingabeformular
'---------------------
Private Sub Abbrechen_Click()
  abgebrochen = True
  Unload Eingabeformular
End Sub

Private Sub Verbuchen_Click()
  'Eingabeformular nach Klick auf "Verbuchen" prüfen
  'Aufwand, etc. sind Textfelder des Eingabeformulars
  'Aufwand="" bedeutet dasselbe wie Aufwand.Value=""
  If Aufwand = "" Or _
    Ertrag = "" Or _
    Aktienkapital = "" Or _
    Reserve = "" Or _
    TantièmeInProzent = ""
  Then
    MsgBox "Keine Leereingaben möglich"
  ElseIf Not (IsNumeric(Aufwand) And _
    IsNumeric(Ertrag) And _
    IsNumeric(Aktienkapital) And _
    IsNumeric(Reserve) And _
    IsNumeric(TantièmeInProzent))
  Then
    MsgBox "Nur numerische Eingaben möglich"
  Else
    Eingabeformular.Hide     'vorübergehend ausblenden
  End If
End Sub

 
Gewinnverteilung.xls
ist komplexer als andere Arbeitsmappen ...

  • weil die Problemstellung der Gewinnverteilung von Aktiengesellschaften schwieriger ist und

  • weil die Benutzeroberfläche neben einem Tabellenblatt und den bereits bekannten vordefinierten Dialogfeldern ein benutzerdefiniertes Eingabeformular mit sieben Steuerelementen umfasst.

Wenn ein Projekt umfangreich ist, lohnt es sich, auf die einzelnen Objekte aus dem Objektkatalog (engl. object browser, Menüpunkt »Ansicht/Objektkatalog) zuzugreifen. Der Objektkatalog zeigt die Objektklassen, Eigenschaften, Methoden, Ereignisse und Konstanten benutzerdefinierter Projekte und vordefinierter Objektbibliotheken von Softwareanbietern.

Der Objektkatalog erleichtert insbesondere den Zugriff auf benutzerdefinierte Objekte. Der folgende Bildschirmausschnitt greift zum Beispiel auf unser Projekt Gewinnverteilung zu. Es besteht aus den Projektkomponenten Arbeitsmappe, Dialogblatt (Tabellenblatt), Eingabeformular und Verarbeitungsmodul. Unter <global> zeigt der Objektkatalog alle Public-Vereinbarungen. Im Bildschirmausschnitt liegt der Cursor auf dem Verarbeitungsmodul. Weil dieses benutzerdefiniert ist, zeigt das rechte Fenster die globale Variable abgebrochen, die lokalen Variablen Ausgabezeile und GV sowie die Methoden BS, Buchungssätze und Start_Click. Im rechten Fenster ist die Methode BS markiert; das untere Fenster zeigt deshalb den Kopf der Prozedur BS.

Neben benutzerdefinierten Projekten können Sie vordefinierte Objektmodelle inspizieren, im besonderen jene von MS Excel und VBA. Der nächste Bildausschnitt positioniert zum Beispiel auf der Eigenschaft Cells des Objekts Worksheet des Objektmodells von MS Excel. Das unterste Fenster gibt an, dass Cells eine Eigenschaft eines Tabellenblatts (engl. worksheet) ist, die ein Range-Objekt mit dem Zellbereich des ganzen Tabellenblatts ergibt.